package Domain;

import Persistence.PersistenceController;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author kenneth
 */
public class ClientConnection_ implements Runnable {

    private Socket link;
    private ObservableManager observableManager;
    private String email;
    private boolean run;
    // Send things to the client
    private ObjectOutputStream objectOutputStream;
    private PrintWriter printWriter;
    // Receive things from the client
    private BufferedReader bufferedReader;
    //hopelijk
    File dir;

    /*
     ** GENERAL
     */
    public ClientConnection_(Socket link, ObservableManager observableManager) {
        this.link = link;
        this.observableManager = observableManager;
        initConnection();
    }

    @Override
    public void run() {
        run = true;

        while (run) {
            String state = "";
            try {
                System.out.println(state);
                state = bufferedReader.readLine();
                System.out.println(state);
            } catch (IOException ex) {
                ex.printStackTrace();
                observableManager.updateMessage("Connection with client failed");
            }
            if (state.equals("login")) {
                login();
            } else if (state.equals("register")) {
                register();
            } else if (state.equals("resetPassword")) {
                resetPassword();
            } else if (state.equals("acceptFile")) {
                acceptFile();
            } else if (state.equals("deleteFile")) {
                deleteFile();
            } //else if(state.equals("checkFile"))
            //checkFile();
            else if (state.equals("synchronize")) {
                synchronizeFiles();
            }
            else if (state.equals("closeConnection")) {
                closeConnection();
            }

            try {
                System.out.println("sleep");
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                run = false;
                Logger.getLogger(ClientConnection.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /*
     ** INITIALIZATION
     */
    private void initConnection() {
        try {
            objectOutputStream = new ObjectOutputStream(link.getOutputStream());
            bufferedReader = new BufferedReader(new InputStreamReader(link.getInputStream()));
            printWriter = new PrintWriter(link.getOutputStream(), true);
        } catch (IOException e) {
            observableManager.updateMessage("Opening streams failed");
        }
    }

    private void closeConnection() {
        try {
            link.close();
            observableManager.updateMessage("Closing " + email + "'s connection");
        } catch (IOException ex) {
            observableManager.updateMessage(email + "'s connection was already closed");
        }
    }

    /*
     ** ACCOUNT
     */
    private void login() {
        String password = "";
        try {
            email = bufferedReader.readLine();
            password = bufferedReader.readLine();
        } catch (IOException ex) {
            ex.printStackTrace();
            observableManager.updateMessage("Connection with client failed");
        }
        boolean loginSuccess = PersistenceController.getInstance().authenticateUser(email, password);
        if (loginSuccess) {
            observableManager.updateMessage("User " + email + " has authenticated");
            checkAndCreateFolder();
        } else {
            observableManager.updateMessage("User " + email + " has failed to authenticate");
        }
        sendResult(loginSuccess);
    }

    private void register() {
        String password = "";
        try {
            email = bufferedReader.readLine();
            password = bufferedReader.readLine();
        } catch (IOException ex) {
            observableManager.updateMessage("Connection with client failed");
        }
        boolean registerSuccess = PersistenceController.getInstance().registerUser(email, password);
        if (registerSuccess) {
            observableManager.updateMessage("User " + email + " has registered an account");
            checkAndCreateFolder();
        } else {
            observableManager.updateMessage("User " + email + " has failed to register an account");
        }
        sendResult(registerSuccess);
    }

    private void resetPassword() {
        try {
            email = bufferedReader.readLine();
        } catch (IOException ex) {
            observableManager.updateMessage("Connection with client failed");
        }
        boolean resetPasswordSuccess = PersistenceController.getInstance().resetPasswordFromUser(email);
        if (resetPasswordSuccess) {
            observableManager.updateMessage("User " + email + " has reseted his password");
        } else {
            observableManager.updateMessage("User " + email + " has failed to reset his password");
        }
        sendResult(resetPasswordSuccess);
    }

    private void checkAndCreateFolder() {
        File dir = new File(email);
        if (!dir.exists()) {
            dir.mkdir();
            observableManager.updateMessage("Client " + email + " has created his rootfolder");
        }
    }

    private void sendResult(boolean result) {
        try {
            objectOutputStream.writeObject((Object) result);
        } catch (IOException ex) {
            observableManager.updateMessage("Connection with client failed");
        }
    }

    //TODO
    public boolean compareFolders() {
        return false;
    }

    /*
     ** CLIENT ACTIONS
     */
    public void acceptFile() {
        try {
            observableManager.updateMessage(email + " is going to upload a file:");
            Path path = Paths.get(email + File.separatorChar + bufferedReader.readLine());
            File file = Utils.createFileAndEmptyParents(path, email);
            observableManager.updateMessage("File name: " + file.getName()/*fileName*/);
            int fileSize = Integer.parseInt(bufferedReader.readLine()); // Receive file size
            observableManager.updateMessage("File size: " + fileSize + " B");

            InputStream is = link.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);
            //File file = new File(email + File.separatorChar + fileName);
            OutputStream os = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(os);
            byte[] buffer = new byte[fileSize];
            int length, totalSize = 0;

            while (!(totalSize == fileSize)) {
                length = bis.read(buffer);
                bos.write(buffer, 0, length);
                totalSize += length;
            }
            sendResult(true);
            bos.flush();
            //bis.close(); 
            bos.close();
            observableManager.updateMessage(email + " uploaded " + file.getName());

            printWriter.println("ok");
        } catch (IOException ex) {
            ex.printStackTrace();
            printWriter.println("fail");
        }
    }

    public void deleteFile(String relatiefPath) throws IOException {
        File file = new File(email, relatiefPath);
        observableManager.updateMessage(email + " deletes file: " + file.getName());

        if (!file.exists()) {
            throw new IllegalArgumentException("Delete: no such file or directory: " + file.getName());
        }

        if (!file.canWrite()) {
            throw new IllegalArgumentException("Delete: write protected: " + file.getName());
        }


        Utils.deleteFileAndEmptyParents(file.toPath(), email);

    }

    public void deleteFile() {
        try {
            deleteFile(bufferedReader.readLine()); // Receive file name
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /*
     private void checkFile()
     {
     try {
     String fileName = bufferedReader.readLine();
     long lastModified1 = Long.parseLong(bufferedReader.readLine());
     String mdstring1 = bufferedReader.readLine();
            
     File file = new File(email + File.pathSeparator + fileName);
     if(file.exists())
     {
     long lastModified2 = Files.getLastModifiedTime(file.toPath()).toMillis();
                
     MessageDigest md = MessageDigest.getInstance("SHA1");
     FileInputStream stream = new FileInputStream(file);
     byte[] dataBytes = new byte[1024];
     int nread = 0; 
     while ((nread = stream.read(dataBytes)) != -1) {
     md.update(dataBytes, 0, nread);
     };
     byte[] mdbytes = md.digest();
     String mdstring2 = mdbytes.toString();
                
     if(mdstring1.equals(mdstring2))
     {
     printWriter.println("synched");
     }
     else
     {
     if(lastModified1 >= lastModified2)
     printWriter.println("changed_client");
     else
     printWriter.println("changed_server");
     }
     }
     else
     {
     printWriter.println("notfound");
     }
     }
     catch(NoSuchAlgorithmException e) {
     e.printStackTrace();
     }
     catch(FileNotFoundException e) {
     e.printStackTrace();
     }
     catch(IOException e) {
     e.printStackTrace();
     }
     }
     */
    /*
     ** SERVER ACTIONS
     */
    public void sendFile(String relatiefPath) {
        File file = new File(email, relatiefPath);
        observableManager.updateMessage("Sending " + file.getName() + " to " + email);
        printWriter.println("acceptFile");
        printWriter.println(dir.toURI().relativize(file.toURI()).getPath()); // Send file name
        printWriter.println(file.length()); // Send file size
        
        
        
        
        
        
        /*byte[] buffer = new byte[(int) file.length()];
        try {
            InputStream is = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            OutputStream os = link.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(os);
            int n;
            while ((n = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, n);
                System.out.println(n);
            }
            bos.flush();
            //bos.close();
            bis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        observableManager.updateMessage("Sent " + file.getName() + " to " + email);
        */
        
        
        
        
        
    }

    public void deleteClientFile(String relatiefPath) {
        printWriter.println("deleteFile");
        File file = new File(email, email + File.pathSeparator + relatiefPath);
        printWriter.println(dir.toURI().relativize(file.toURI()).getPath());
    }

    /*
     ** SYNCHRONIZATION
     */
    public void synchronizeFiles() {
        observableManager.updateMessage(email + " synchronizing files...");

        dir = new File(this.email);
        List<File> fileList = Utils.getFileList(dir);

        for (File file : fileList) {
            try {
                printWriter.println("file");
                printWriter.println(dir.toURI().relativize(file.toURI()).getPath());
                printWriter.println(Files.getLastModifiedTime(file.toPath()).toMillis());
                printWriter.println(Utils.calculateFileHash(file));
                
                String fileStatus = bufferedReader.readLine();
                System.out.println(dir.toURI().relativize(file.toURI()).getPath() + " : " + fileStatus);
                
                if (fileStatus.equals("changed_client")) {
                    deleteFile(dir.toURI().relativize(file.toURI()).getPath());
                    if(bufferedReader.readLine().equals("acceptFile"))
                        acceptFile();
                } else if (fileStatus.equals("changed_server")) {
                    sendFile(dir.toURI().relativize(file.toURI()).getPath());
                } else if (fileStatus.equals("notfound")) {
                    sendFile(dir.toURI().relativize(file.toURI()).getPath());
                    System.out.println("uploaded");
                }
            } catch (IOException e) {
                e.printStackTrace();
                observableManager.updateMessage("Error synchronizing file: " + file.getName());
            }
        }
        
        System.out.println("END!!!!!!!!!!!!");
        printWriter.println("end");
    }
}
